/* stmpde.c */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>

#ifdef _WIN32
#include "Packet32.h"
#include <windowsx.h>
#include <ntddndis.h>
#else /* _WIN32 */
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netpacket/packet.h>
#include <pthread.h>
#include <semaphore.h>
#include <termios.h>
#endif /* _WIN32 */

#include <pcap.h>

#include "khash.h"

#define APP_MODE_ECHO	0
#define APP_MODE_RAW	1
#define APP_MODE_DNS	2
#define APP_MODE_FTP	3
#define APP_MODE_HTTP	4
/* UNDONE */
#define APP_MODE_TELNET	5

#define APP_MODE_FIRST	APP_MODE_ECHO
#define APP_MODE_LAST	APP_MODE_HTTP

#define ARP_TIMEOUT		1 /* as sec. */
/* surpisingly the same are for tcp */
#define UDP_EPHEMP_MIN	5000
#define UDP_EPHEMP_MAX	65535

#define NS_PORT_DEFAULT		53
#define FTP_PORT_DEFAULT	21
#define HTTP_PORT_DEFAULT	80
#define TELNET_PORT_DEFAULT	23
#define ECHO_PORT_DEFAULT	7 /* echo. also, can be discard(9), daytime(13),
								chargen(19) or time(37) */

#define PKT_TO_FETCH	1000
#define ENDP_NUM_TRIES	10

#ifndef MIN
#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
#endif /* MIN */

#define DEFAULT_MSS_SIZE	1460

static inline unsigned short get_app_port(unsigned char app_mode)
{
	switch (app_mode) {
	case APP_MODE_ECHO :
	case APP_MODE_RAW : return (ECHO_PORT_DEFAULT);
	case APP_MODE_DNS : return (NS_PORT_DEFAULT);
	case APP_MODE_FTP : return (FTP_PORT_DEFAULT);
	case APP_MODE_HTTP : return (HTTP_PORT_DEFAULT);
	case APP_MODE_TELNET : return (TELNET_PORT_DEFAULT);
	}

	assert(0);

	return (0);
}

static inline int get_app_snaplen(unsigned char app_mode)
{
	switch (app_mode) {
		case APP_MODE_FTP : return (116); /* 227 Entering Passive Mode */
		case APP_MODE_HTTP : return (82); /* server response code */
	}

	return (68);
}

/*
 * Internet checksum
 */

/* GLib essentials */
typedef unsigned char guint8;
typedef unsigned short guint16;
typedef unsigned int guint32;

/* ripped off of wireshark */
typedef struct {
	const guint8 *ptr;
	int	len;
} vec_t;

#define ADDCARRY(x)  {if ((x) > 65535) (x) -= 65535;}
#define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; ADDCARRY(sum);}

int
in_cksum(const vec_t *vec, int veclen)
{
	register const guint16 *w;
	register int sum = 0;
	register int mlen = 0;
	int byte_swapped = 0;

	union {
		guint8	c[2];
		guint16	s;
	} s_util;
	union {
		guint16 s[2];
		guint32	l;
	} l_util;

	for (; veclen != 0; vec++, veclen--) {
		if (vec->len == 0)
			continue;
		w = (const guint16 *)(const void *)vec->ptr;
		if (mlen == -1) {
			/*
			 * The first byte of this chunk is the continuation
			 * of a word spanning between this chunk and the
			 * last chunk.
			 *
			 * s_util.c[0] is already saved when scanning previous
			 * chunk.
			 */
			s_util.c[1] = *(const guint8 *)w;
			sum += s_util.s;
			w = (const guint16 *)(const void *)((const guint8 *)w + 1);
			mlen = vec->len - 1;
		} else
			mlen = vec->len;
		/*
		 * Force to even boundary.
		 */
		if ((1 & (unsigned long) w) && (mlen > 0)) {
			REDUCE;
			sum <<= 8;
			s_util.c[0] = *(const guint8 *)w;
			w = (const guint16 *)(const void *)((const guint8 *)w + 1);
			mlen--;
			byte_swapped = 1;
		}
		/*
		 * Unroll the loop to make overhead from
		 * branches &c small.
		 */
		while ((mlen -= 32) >= 0) {
			sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
			sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];
			sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11];
			sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15];
			w += 16;
		}
		mlen += 32;
		while ((mlen -= 8) >= 0) {
			sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
			w += 4;
		}
		mlen += 8;
		if (mlen == 0 && byte_swapped == 0)
			continue;
		REDUCE;
		while ((mlen -= 2) >= 0) {
			sum += *w++;
		}
		if (byte_swapped) {
			REDUCE;
			sum <<= 8;
			byte_swapped = 0;
			if (mlen == -1) {
				s_util.c[1] = *(const guint8 *)w;
				sum += s_util.s;
				mlen = 0;
			} else
				mlen = -1;
		} else if (mlen == -1)
			s_util.c[0] = *(const guint8 *)w;
	}
	if (mlen == -1) {
		/* The last mbuf has odd # of bytes. Follow the
		   standard (the odd byte may be shifted left by 8 bits
		   or not as determined by endian-ness of the machine) */
		s_util.c[1] = 0;
		sum += s_util.s;
	}
	REDUCE;
	return (~sum & 0xffff);
}


/*
 * Messing with routing
 */

static int get_route_to_host(const struct in_addr *dst_ip, struct in_addr *next_hop,
	struct in_addr *src_ip, char *dev_name)
{
#ifdef _WIN32
#error UNDONE
#else /* _WIN32 */
	FILE *fp = NULL;
	char run_buf[64], read_buf[256], dst_buf[16];
	const char *pc;
	int result = ENOENT, len;

	strcpy(dst_buf, inet_ntoa(*dst_ip));
	len = strlen(dst_buf);
	snprintf(run_buf, sizeof (run_buf) - 1, "ip route get %s", dst_buf);

	fp = popen(run_buf, "r");
	if (!fp) {
		fprintf(stderr, "** popen() failed with %d\n", errno);
		return errno;
	}

	while (fgets(read_buf, sizeof (read_buf) - 1, fp)) {
		if (strncmp(read_buf, dst_buf, len) == 0) {
loc_contlocal:
			pc = strstr(read_buf + len, " src ");
			if (!pc || !inet_aton(pc + 5, src_ip)) {
				result = EINVAL;
				break;
			}
			pc = strstr(read_buf + len, " via ");
			if (pc) {
				if (!inet_aton(pc + 5, next_hop)) {
					result = EINVAL;
					break;
				}
			} else {
				*next_hop = *dst_ip;
			}
			pc = strstr(read_buf + len, " dev ");
			if (pc) {
				sscanf(pc, " dev %s ", dev_name);
			}
			result = 0;
			break;
		} else if (strncmp(read_buf, "local ", 6) == 0 &&
			strncmp(read_buf + 6, dst_buf, len) == 0) {
			len += 6;
			goto loc_contlocal;
		} 
	}

	pclose(fp);

	return (result);
#endif /* _WIN32 */
}

#ifndef _WIN32
/*-------------------*
 * timespec_add_ns32 *
 *-------------------*/

static inline void timespec_add_ns32(struct timespec* pts, long ns)
{
	ldiv_t ld = ldiv(pts->tv_nsec + ns, 1000000000l); /* nsec per sec */
	pts->tv_sec += ld.quot;
	pts->tv_nsec = ld.rem;
}
#endif /* _WIN32 */

/*---------------*
 * faked streams *
 *---------------*/

/* stages */
#define FKS_SPAWN		0
#define FKS_SYN_SENT	1
#define FKS_SYN_ACKD	2
#define FKS_CONNECTD	3
/* app-generic */
#define FKS_HLO_RCVD	4
#define FKS_REQ_PSHD	5
#define FKS_REQ_ACKD	6
/* app-specific */
#define FTP_USR_ALWD	7 /* 331 user ok */
#define FTP_PAS_ACPT	8 /* 230 user logged in */
#define FTP_PSV_INIT	9 /* 227 passive mode */
#define FTP_DCO_SETP	10 /* data connection esnablished */
#define FTP_TRA_COMP	11 /* 226 transfer complete */
/* app-generic again */
#define FKS_ANS_RCVD	12
#define FKS_FIN_RCVD	FKS_ANS_RCVD
#define FKS_FIN_SENT	13
#define FKS_CLOSED		14

/* flags, persistent */
#define FKS_PERS_FIN1	0x0100 /* FIN sent */
#define FKS_PERS_FIN2	0x0200 /* FIN received */
#define FKS_PERS_FIN3	0x0400 /* FIN acknowledged */
#define FKS_PERS_DATA	0x0800 /* FTP data connection */

#define FKS_FINS_ALL	(FKS_PERS_FIN1 | FKS_PERS_FIN2 | FKS_PERS_FIN3)

/* flags, misc. */
#define FKS_NEED_ACK	0x1000
/* upper-level proto errors */
#define FKS_UPER_USR	0x2000 /* e.g. FTP 530 */
#define FKS_UPER_DCO	0x4000 /* e.g. FTP data connection error */

#define FKS_TIMO_DROP	30 /* default timeout, as sec. */

typedef vec_t appreq_t;

typedef struct fkstream_s
{
	/* time stamps */
	unsigned int ts_sec,
		ts_spawn; /* sec., to linger properly */

	unsigned int seq, ack;

	struct in_addr ip; /* source ip */
	unsigned short src_port, dst_port;

	/* UNDONE: split with bit fields */
	unsigned short stage; /* united with flags */

	/* application */
	union {
		struct {
			unsigned short iname,
				trans_id; /* to match answers */
		};
		unsigned int lp_key; /* FTP, corresponding control/data connection */
		struct in_addr dst_ip; /* echo app mode */
	};

} fkstream_t;

KHASH_MAP_INIT_INT64(fksname, fkstream_t);

/* frame context -- also, frame statistics */
typedef struct frame_ctx_s
{
	unsigned char src_mac[6], dst_mac[6];
	struct sockaddr_in sa_dst;
	unsigned short /* ip packet id */id, length;
	unsigned char app_mode, proto, *raw_data, *ether_hdr, *ip_hdr, *trans_hdr;
	unsigned short *ip_len, *ip_cksum, *trans_cksum,
		ether_hdr_len, ip_hdr_len, trans_len;
	unsigned long long rcount, num_spawn;
	unsigned int /* timeout on stream */app_timo, linger_time; 
	pcap_t *pcap_h; /* pcap output device */
	appreq_t *areqs; /* app requests */
	char user_name[32], passwd[32];
	unsigned short err_notify;
	int cksum_f; /* force checksum on UDP */

} frame_ctx_t;


static inline uint64_t hap_key(unsigned int ip, unsigned short port1, unsigned short port2,
	unsigned char app_mode)
{
	uint64_t key = ((uint64_t) ntohl(ip) << 16) | port1;

	if (app_mode == APP_MODE_FTP)
		key = (key << 16) | port2;

	return (key);
}


static inline int ip_frame(frame_ctx_t *fc, struct in_addr *src_ip, struct in_addr *dst_ip)
{
	unsigned char *pb = fc->ip_hdr;

	/* IP header */
	/* UNDONE: size hardcoded, rework */
	*pb++ = 0x45; /* type = 4, length = 20 */
	*pb++ = 0; /* TOS */
	fc->ip_len =(unsigned short *) pb; pb += 2; /* length */
	*(unsigned short *) pb = htons(fc->id++); pb += 2; /* id */
	*pb++ = 0x40; /* flags = 0x2 (DONT_FRAGMENT), fragment offset = 0 */
	*pb++ = 0; /* fragment offset = 0 */
	*pb++ = 0x40; /* TTL = 64 */
	*pb++ = fc->proto; /* protocol */
	fc->ip_cksum =(unsigned short *) pb; pb += 2; /* checksum */
	*(unsigned int *) pb = src_ip->s_addr; pb += 4; /* src ip */
	*(unsigned int *) pb = dst_ip->s_addr; pb += 4; /* dst ip */

	fc->ip_hdr_len = pb - fc->ip_hdr;
	fc->trans_hdr = pb; 

	return (fc->ip_hdr_len);
}


static inline int tcp_frame(frame_ctx_t *fc, fkstream_t *sp, unsigned short fl)
{
	unsigned char *pb = fc->trans_hdr, *pdo, *pad;

	*(unsigned short *) pb = sp->src_port; pb += 2; /* src port */
	*(unsigned short *) pb = sp->dst_port; pb += 2; /* dst port */
	*(unsigned int *) pb = htonl(sp->seq); pb += 4; /* seq */
	*(unsigned int *) pb = htonl(sp->ack); pb += 4; /* ack */
	pdo = pb++;
	*pb++ = fl & 0xff; /* flags */
	*(unsigned short *) pb = htons(0xfff); pb += 2; /* window size = 4095 */
	fc->trans_cksum =(unsigned short *) pb; pb += 2; /* checksum */
	*(unsigned short *) pb = 0x0; pb += 2; /* urgent pointer */
/* --> add options here */
	*pb++ = 0x02; /* option - MSS */
	*pb++ = 0x04; /* length = 4 */
	*(unsigned short *) pb = htons(DEFAULT_MSS_SIZE); pb += 2; /* MSS size */

/* keep it the last  */
	for (pad = fc->trans_hdr + (((size_t)(pb - fc->trans_hdr) + 3) & ~3); pb < pad;
		*pb++ =/* option - NOP */0x01);

	*pdo =(unsigned char) ((((pb - fc->trans_hdr) >> 2) << 4) |
		((fl >> 8) & 0xf)); /* data offset, flags */

	fc->trans_len = pb - fc->trans_hdr;

	return (fc->trans_len);
}


static inline int udp_frame(frame_ctx_t *fc, fkstream_t *sp, unsigned short user_len)
{
	unsigned char *pb = fc->trans_hdr;

	*(unsigned short *) pb = sp->src_port; pb += 2; /* src port */
	*(unsigned short *) pb = sp->dst_port; pb += 2; /* dst port */
	*(unsigned short *) pb = htons(user_len); pb += 2; /* length */
	fc->trans_cksum =(unsigned short *) pb; pb += 2; /* checksum */

	fc->trans_len = user_len;

	return (fc->trans_len);
}


static inline void cksum_frame(frame_ctx_t *fc)
{
	vec_t cksum_vec[4];
	unsigned int phdr; /* pseudo header */

	*fc->ip_cksum = 0;
	cksum_vec[0].ptr = fc->ip_hdr;
	cksum_vec[0].len = fc->ip_hdr_len;
	*fc->ip_cksum = in_cksum(cksum_vec, 1);

	*fc->trans_cksum = 0;
	if (fc->proto == IPPROTO_UDP && !fc->cksum_f) {
		/* skip on it */
	} else {
		unsigned trans_len = fc->length - fc->ether_hdr_len - fc->ip_hdr_len;
		cksum_vec[0].ptr = fc->ip_hdr + 12; /* src ip */
		cksum_vec[0].len = 4;
		cksum_vec[1].ptr = fc->ip_hdr + 16; /* dst ip */
		cksum_vec[1].len = 4;
		phdr = htonl(((unsigned int) fc->proto << 16) | trans_len);
		cksum_vec[2].ptr = (unsigned char *) &phdr;
		cksum_vec[2].len = 4;
		cksum_vec[3].ptr = fc->trans_hdr;
		cksum_vec[3].len = trans_len;
		*fc->trans_cksum = in_cksum(cksum_vec, 4);
	}
}


static inline int fks_user_push(frame_ctx_t *fc, fkstream_t *sp, unsigned char fl,
	unsigned char *user, int user_len)
{
	ip_frame(fc, &sp->ip, &fc->sa_dst.sin_addr);
	
	tcp_frame(fc, sp, fl);

	if (user) memcpy(fc->raw_data + fc->ether_hdr_len + fc->ip_hdr_len + fc->trans_len,
		user, user_len);

	fc->length = fc->ether_hdr_len + fc->ip_hdr_len + fc->trans_len + user_len; 
	*fc->ip_len = htons(fc->ip_hdr_len + fc->trans_len + user_len);

	cksum_frame(fc);

	if (pcap_sendpacket(fc->pcap_h, fc->raw_data, fc->length)) {
		fprintf(stderr, "** pcap_sendpacket() failed (%s)\n", pcap_geterr(fc->pcap_h));
		return (-1);
	}

	if (fl & /* SYN | FIN */3)
		sp->seq++;

	sp->seq += user_len;

	return (0);
}


static inline void fks_tick(khash_t(fksname) *nodes, frame_ctx_t *fc, struct timespec* pts)
{
	khiter_t it;
	fkstream_t *sp;
	unsigned char fl, *user;
	int delta, user_len;
	unsigned short st, next_st, pers, err;
	char misc_req[64];
	
	for (it = kh_begin(nodes); it != kh_end(nodes); ++it) {
		if (!kh_exist(nodes, it))
			continue;

		sp = &kh_value(nodes, it);
		fl = 0; user = NULL; user_len = 0;
		delta = pts->tv_sec - sp->ts_sec;
		st = next_st = (sp->stage & 0xff);
		pers = (sp->stage & 0xf00);
		err = (sp->stage & 0xf000);

		/* notify on errors */
		if (err && (fc->err_notify & err) != err) {
			if (err & FKS_UPER_USR)
				fprintf(stdout, "** not logged in: bad user name or password\n");
			if (err & FKS_UPER_DCO)
				fprintf(stdout, "** data connection error\n");
			fc->err_notify |= err;
		}

		if (st == FKS_CLOSED) {
			kh_del(fksname, nodes, it);
			if (!(pers & FKS_PERS_DATA)) fc->num_spawn--;
			continue;
		} else if (delta > fc->app_timo) {
			fl = /* RST | ACK */0x14;
			next_st = FKS_CLOSED;
		} else if (st == FKS_SPAWN) {
			sp->seq = ((unsigned int) rand() << 16) | (unsigned int) rand();
			sp->ack = 0;
			fl = /* SYN */0x2; 
			next_st = FKS_SYN_SENT;
			if (!(pers & FKS_PERS_DATA)) fc->num_spawn++;
		} else if (st == FKS_SYN_SENT) {
		} else if (st == FKS_SYN_ACKD) {
			if (fc->app_mode == APP_MODE_FTP && (pers & FKS_PERS_DATA)) {
				/* shortcut on hap_key() */
				uint64_t cc_key = (((uint64_t) ntohl(sp->ip.s_addr) << 32) | sp->lp_key);
				khiter_t cc_it = kh_get(fksname, nodes, cc_key);
				if (cc_it != kh_end(nodes) && kh_exist(nodes, cc_it)) {
					fkstream_t *cc_sp = &kh_value(nodes, cc_it);
					cc_sp->stage = FTP_DCO_SETP;
				} else {
					/* UNDONE: close on error */
				}
			}
			next_st = FKS_CONNECTD;
		} else if (st == FKS_CONNECTD) {
			if (fc->app_mode != APP_MODE_FTP)
				next_st = FKS_HLO_RCVD; /* skip on welcome 220 */
		} else if (st == FKS_HLO_RCVD) {
			if (fc->app_mode == APP_MODE_FTP) {
				user_len = snprintf(misc_req, sizeof (misc_req) - 1, "USER %s\r\n",
					fc->user_name);
				user =(unsigned char *) misc_req;
			} else {
				user =(unsigned char *) fc->areqs[sp->iname].ptr;
				user_len = fc->areqs[sp->iname].len;
				if (fc->app_mode == APP_MODE_DNS) {
					sp->trans_id =(unsigned short) rand();
					*(unsigned short *) (user + 2) = sp->trans_id; /* put transaction id */
				}
			}
		} else if (st == FTP_USR_ALWD) {
			user_len = snprintf(misc_req, sizeof (misc_req) - 1, "PASS %s\r\n",
				fc->passwd);
			user =(unsigned char *) misc_req;
		} else if (st == FTP_PAS_ACPT) {
			user_len = snprintf(misc_req, sizeof (misc_req) - 1, "PASV\r\n");
			user =(unsigned char *) misc_req;
		} else if (st == FTP_DCO_SETP) {
			/* UNDONE: make chained requests possible */
			user =(unsigned char *) fc->areqs[0].ptr;
			user_len = fc->areqs[0].len;
		} else if (st == FTP_TRA_COMP) {
			if (!fc->linger_time || (pts->tv_sec - sp->ts_spawn) > fc->linger_time) {
				user_len = snprintf(misc_req, sizeof (misc_req) - 1, "QUIT\r\n");
				user =(unsigned char *) misc_req;
			}
		} else if (st == FKS_ANS_RCVD) { /* also FKS_FIN_RCVD */
			if (!fc->linger_time || fc->app_mode > APP_MODE_DNS ||
				(pts->tv_sec - sp->ts_spawn) > fc->linger_time) {
				fl = /* FIN | ACK */0x11;
				next_st = FKS_FIN_SENT;
				pers |= FKS_PERS_FIN1;
			}
		} else if (st == FKS_FIN_SENT) {
			if ((pers & FKS_FINS_ALL) == FKS_FINS_ALL) {
				if (!(pers & FKS_PERS_DATA)) fc->rcount++;
				next_st = FKS_CLOSED;
			}
		}

		if (user && user_len) {
			fl = /* PSH | ACK */0x18;
			next_st = FKS_REQ_PSHD;
		}

		if (sp->stage & FKS_NEED_ACK)
			fl |= /* ACK */0x10;

		if ((pers & FKS_PERS_FIN2) && (fl & /* ACK */0x10))
			pers |= FKS_PERS_FIN3;

		if (!fl || fks_user_push(fc, sp, fl, user, user_len) == 0)
			sp->stage = (next_st | pers);
	}
}

#ifdef DEBUG
static void fks_dump(khash_t(fksname) *nodes)
{
	khiter_t it;
	fkstream_t *sp;

	fprintf(stdout, "-------------------\n");

	for (it = kh_begin(nodes); it != kh_end(nodes); ++it) {
		if (!kh_exist(nodes, it))
			continue;

		sp = &kh_value(nodes, it);

		fprintf(stdout, "%d.%d.%d.%d %d 0x%x\n",
			(sp->ip.s_addr >> 0) & 255,
			(sp->ip.s_addr >> 8) & 255,
			(sp->ip.s_addr >> 16) & 255,
			(sp->ip.s_addr >> 24) & 255,
			ntohs(sp->src_port), sp->stage);
	}
}
#endif /* DEBUG */

static inline void parse_foreign_frame(struct pcap_pkthdr *pkt_hdr, const unsigned char *pkt_data,
	khash_t(fksname) *nodes, struct timespec* pts, unsigned char app_mode)
{
	unsigned int dst_ip = *(unsigned int *) (pkt_data + 0x1e); /* ip hdr */
	unsigned short src_port = *(unsigned short *) (pkt_data + 0x22), /* tcp hdr */
		dst_port = *(unsigned short *) (pkt_data + 0x24);
	uint64_t key = hap_key(dst_ip, dst_port, src_port, app_mode);
	khiter_t it = kh_get(fksname, nodes, key);

	if (it != kh_end(nodes) && kh_exist(nodes, it)) {
		fkstream_t *sp = &kh_value(nodes, it);
		unsigned short total_len = ntohs(*(unsigned short *) (pkt_data + 0x10)), /* ip total len */
			user_len = total_len - ((*(pkt_data + 0x0e) & 0xf) << 2) - /* ip hdr len */
				((*(pkt_data + 0x2e) & 0xf0) >> 2), /* tcp hdr len */
			pers = (sp->stage & 0xf00);
		unsigned int seq = ntohl(*(unsigned int *) (pkt_data + 0x26)) + user_len, /* incoming seq */
			ack = ntohl(*(unsigned int *) (pkt_data + 0x2a)); /* incoming ack */
		unsigned char flags = *(pkt_data + 0x2f); /* LSB portion */
		const unsigned char *user = pkt_data +
			/* eth hdr len */14 + total_len - user_len;

		sp->ts_sec = pts->tv_sec;
		if (seq > sp->ack) sp->ack = seq;

		switch (sp->stage & 0xff) {
		case FKS_SYN_SENT :
			if ((flags & /* SYN | ACK */0x12) == 0x12 && ack >= sp->seq) {
				sp->stage = FKS_SYN_ACKD;
			}
			break;
		case FKS_CONNECTD :
			if (user_len && app_mode == APP_MODE_FTP && memcmp(user, "220 ", 4) == 0) {
				sp->stage = FKS_HLO_RCVD;
			}
			break;
		case FKS_REQ_PSHD :
			if ((flags & /* ACK */0x10) && ack >= sp->seq) {
				sp->stage = FKS_REQ_ACKD;
				/* fall-through */
			} else {
				break;
			}
		case FKS_REQ_ACKD :
			if (user_len) {
				if (app_mode == APP_MODE_FTP) {
					if (memcmp(user, "331 ", 4) == 0) {
						sp->stage = FTP_USR_ALWD;
					} else if (memcmp(user, "230 ", 4) == 0) {
						sp->stage = FTP_PAS_ACPT;
					} else if (memcmp(user, "221 ", 4) == 0) {
						sp->stage = FKS_ANS_RCVD;
					} else if (memcmp(user, "226 ", 4) == 0) {
						sp->stage = FTP_TRA_COMP;
					} else if (memcmp(user, "227 ", 4) == 0) {
						unsigned int h1, h2, h3, h4, p1, p2;
						if (sscanf((const char *) (user + 4),
							"Entering Passive Mode (%u,%u,%u,%u,%u,%u)",
							&h1, &h2, &h3, &h4, &p1, &p2) == 6) {
							unsigned short dc_dport = p1 | (p2 << 8);
							int res = 0, dcp_tries = 0;
							do {
								unsigned short dc_sport =(unsigned short) rand();
								uint64_t dc_key = hap_key(dst_ip, dc_sport, dc_dport, app_mode);
								khiter_t dc_it = kh_get(fksname, nodes, dc_key);
								if (dc_it == kh_end(nodes)) {
									dc_it = kh_put(fksname, nodes, dc_key, &res);
									if (res) {
										fkstream_t *dc_sp = &kh_value(nodes, dc_it);
										dc_sp->ip.s_addr = dst_ip;
										dc_sp->src_port = dc_sport;
										dc_sp->dst_port = dc_dport;
										dc_sp->stage = FKS_PERS_DATA | FKS_SPAWN;
										dc_sp->ts_sec = pts->tv_sec;
										dc_sp->ts_spawn = pts->tv_sec;
										dc_sp->lp_key =(uint32_t) key;
										sp->lp_key =(uint32_t) dc_key;
									} else {
										kh_del(fksname, nodes, dc_it);
									}
									break;
								}
							} while (dcp_tries++ < ENDP_NUM_TRIES);
							sp->stage = (res ? FTP_PSV_INIT : FTP_TRA_COMP | FKS_UPER_DCO);
						} else {
							sp->stage = FTP_TRA_COMP | FKS_UPER_DCO;
						}
					} else if (memcmp(user, "530 ", 4) == 0) {
						sp->stage = FTP_TRA_COMP | FKS_UPER_USR;
					}
				} else if (app_mode == APP_MODE_DNS) {
					if (*(unsigned short *) (user + 2) == sp->trans_id)
						sp->stage = FKS_ANS_RCVD;
				} else {
					/*assert(0);*/
				}
			}
			break;
		}

		if (flags & /* SYN | FIN */3) {
			sp->ack++;
			if (flags & /* FIN */1) {
				if ((sp->stage & 0xff) < FKS_FIN_SENT)
					sp->stage = FKS_FIN_RCVD;
				sp->stage |= FKS_PERS_FIN2;
			}
		} else if (flags & /* RST */4) {
			sp->stage = FKS_CLOSED;
		}

		if (user_len || (flags & /* SYN | FIN */3))
			sp->stage |= FKS_NEED_ACK;

		sp->stage |= pers;
	}
}


/*------*
 * main *
 *------*/

int main(int argc, char *argv[])
{
	int i, last_switch_f = 0, *param_int = NULL, run_time = 0, packet_rate = 0,
		num_requests = 0, len, found_f, err, j, k, res, tofetch, snaplen = 0, cksum_f = 0,
		app_mode = APP_MODE_DNS, app_timo = FKS_TIMO_DROP, linger_time = 0, endp_tries;
	const char **param_str = NULL, *targ_str = NULL, *query_str = NULL, *ua_str = NULL,
		*sstart_str = NULL, *slim_str = NULL, *iface_name = NULL, *pc, *pn;
	char *parg, *pend = NULL, ch, tmpbuf[64], errbuf[PCAP_ERRBUF_SIZE], dev_name[64] = {0};
	const char dns_chars[] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	const int dns_parts[] = {8,2,3}; /* for generated names */
	unsigned char arp_req[1024], packet_data[1600], *pb;
	unsigned short port_ival = 0, port_start, p_val, *udp_len, *dns_len;
	unsigned int src_ival = 0, ip_start, ip_val;
	struct sockaddr_in sa_src, sa_start, sa_limit;
	struct in_addr next_hop;
	pcap_if_t *alldevs, *d;
	frame_ctx_t fc;
	time_t tmark;
	bpf_u_int32 net_addr, net_mask;
	struct bpf_program fcode;
	const unsigned char *pkt_data;
	struct pcap_pkthdr *pkt_hdr;
	appreq_t apr;
#ifdef _WIN32
	LPADAPTER adapter;
	PPACKET_OID_DATA OidData;
	ULONG IoCtlBuffLen;
#else /* _WIN32 */
	struct sockaddr_ll sa_dev;
	fd_set ralert;
	int raw_fd;
	struct timeval arp_to;
	struct ifreq reqbuf;
	struct timespec ts, stop_ts, inter_ts;
	struct termios kbd_mode, kbd_mode_orig;
#endif /* _WIN32 */

    khash_t(fksname) *fks_nodes = kh_init(fksname);

	if (argc > 1) {
		for (i = 1; i < argc; i++) {
			parg = argv[i];
			if (!parg) {
				continue;
			}
			if (!last_switch_f && *parg == '-') {
				if (!strcmp(parg++, "--")) {
					last_switch_f = 1;
					continue;
				}
				while (parg && (ch = *parg++)) {
					switch (ch) {
					case 'a' :	param_int = &app_mode; goto loc_getargint;
					case 'c' :	param_int = &num_requests; goto loc_getargint;
					case 'd' :	param_int = &run_time; goto loc_getargint;
					case 'e' :	app_mode = APP_MODE_ECHO;
								param_str = &iface_name; goto loc_getargstr;
					case 'k' :	cksum_f = 1; break;
					case 'l' :	param_str = &slim_str; goto loc_getargstr;
					case 'n' :	param_int = &linger_time; goto loc_getargint;
					case 'o' :	param_int = &app_timo; goto loc_getargint;
					case 'p' :	param_int = &snaplen; goto loc_getargint;
					case 'q' :	param_str = &query_str; goto loc_getargstr;
					case 's' :	param_str = &sstart_str; goto loc_getargstr;
					case 't' :	param_int = &packet_rate; goto loc_getargint;
					case 'u' :	param_str = &ua_str; goto loc_getargstr;
					default	:	goto loc_showusage;
loc_getargint:
						if (param_int) {
							if (!*parg) {
								if (i + 1 < argc) {
									*param_int = atoi(argv[++i]);
								} else {
									goto loc_showusage;
								}
							} else {
								*param_int = strtol(parg, &pend, 10);
								if (parg == pend)
									goto loc_showusage;
								parg = pend;
							}
						}
						break;
loc_getargstr:
						if (param_str) {
							if (!*parg) {
								if (i + 1 < argc) {
									*param_str = argv[++i];
								} else {
									goto loc_showusage;
								}
							} else {
								*param_str = parg;
								parg = parg + strlen(parg);
							}
							if (!*param_str || !strlen(*param_str))
								goto loc_showusage;
						}
						break;
					}
				}
			} else if (!targ_str) {
				targ_str = argv[i];
			} else {
				goto loc_showusage;
			}
		}
	} else {
loc_showusage:
		fprintf(stderr, "Usage: STMPDE <switches> [<tcp|udp|ftp|http>://[username[:password]@]]<listen_ip[:port]>\n"
			"built " __DATE__ " " __TIME__ "\n"
			"Generates (or serves) app queries in great numbers\n" /* this app can break */
			"*PLEASE DO NOT USE WITH PUBLIC RESOURCES*\n"
			"Switches:\n"
			"  -a <modenum>\t\tapp mode:\n"
			"\t\t\t  0=echo, 1=raw, 2=dns (default), 3=ftp, 4=http\n"
			"  -c <num>\t\tnumber of requests to spew\n"
			"  -d <sec>\t\trun for specified time\n"
			"  -k \t\t\tforce udp checksum\n"
			"  -l <ipaddr[:port]>\tset the limit for source interval (not inclusive!)\n"
			"  -n <sec>\t\thow long single session will last (default: instant drop)\n"
			"  -o <sec>\t\ttimeout on failed sessions (default: %d sec)\n"
			"  -p <num>\t\toverride snap length (don't mess with it!)\n"
			"  -q <string>\t\tset query, such as name for dns, or (relative) url for http\n"
			"  -s <ipaddr[:port]>\tset source ip address\n"
			"  -t <rate>\t\tset queries rate, as packets/connections (per sec)\n"
			"  -u <string>\t\tuser agent, for http queries (default: MSIE6 on WinXP SP2)\n"
			, FKS_TIMO_DROP);

		return (EINVAL); /* usage */
	}

	if (app_mode < APP_MODE_FIRST || app_mode > APP_MODE_LAST) {
		fprintf(stderr, "** invalid app mode\n");
		return (EINVAL);
	}

	if (app_timo < 1 || linger_time < 0) {
		fprintf(stderr, "** invalid timeout\n");
		return (EINVAL);
	}

	memset(&fc, 0, sizeof (fc));
	fc.app_mode =(unsigned char) app_mode;
	fc.proto = (fc.app_mode > APP_MODE_DNS ? IPPROTO_TCP : IPPROTO_UDP);
	fc.app_timo = app_timo;
	fc.linger_time = linger_time;
	fc.cksum_f = cksum_f;

	memset(&fc.sa_dst, 0, sizeof (fc.sa_dst));
	fc.sa_dst.sin_family = AF_INET;

	pc = targ_str;

	if (!pc || !*pc) {
		fprintf(stderr, "** bad input for target\n");
		return (EINVAL);
	}

	if (strncasecmp(pc, "udp://", 6) == 0) {
		fc.proto = IPPROTO_UDP;
		pc += 6;
	} else if (strncasecmp(pc, "tcp://", 6) == 0) {
		fc.proto = IPPROTO_TCP;
		pc += 6;
	} else if (strncasecmp(pc, "ftp://", 6) == 0) {
		fc.proto = IPPROTO_TCP;
		fc.app_mode = APP_MODE_FTP;
		pc += 6;
	} else if (strncasecmp(pc, "http://", 7) == 0) {
		fc.proto = IPPROTO_TCP;
		fc.app_mode = APP_MODE_HTTP;
		pc += 7;
	}

	if (fc.proto == IPPROTO_UDP && fc.app_mode > APP_MODE_DNS) {
		fprintf(stderr, "** bad proto override\n");
		return (EINVAL);
	}

	for (pn = pc; *pn && *pn != '@'; pn++);

	if (*pn) {
		for (i = 0; pc < pn && *pc != ':' && i < sizeof (fc.user_name) - 2; i++)
			fc.user_name[i] = *pc++;

		fc.user_name[i] = 0;

		if (*pc++ == ':') {
			for (i = 0; pc < pn && i < sizeof (fc.passwd) - 2; i++)
				fc.passwd[i] = *pc++;

			fc.passwd[i] = 0;
		}

		pc = pn + 1;
	}

	for (pn = pc; *pn && *pn != ':'; pn++);

	len = MIN((pn - pc), (sizeof (tmpbuf) - 2));
	strncpy(tmpbuf, pc, len);
	tmpbuf[len] = 0;

	if (!inet_aton(tmpbuf, &fc.sa_dst.sin_addr)) {
		fprintf(stderr, "** bad input for target\n");
		return (EINVAL);
	}
	
	fc.sa_dst.sin_port = htons((*pn++ && *pn) ? atoi(pn) : get_app_port(fc.app_mode));

	memset(&sa_src, 0, sizeof (sa_src));
	sa_src.sin_family = AF_INET;

	if (fc.app_mode == APP_MODE_ECHO) {
		struct ifaddrs *ifas, *ia;
		if (getifaddrs(&ifas) == 0) {
			for (ia = ifas; ia; ia = ia->ifa_next) {
				if (ia->ifa_addr && ia->ifa_addr->sa_family == AF_INET) {
					struct sockaddr_in *sa =(struct sockaddr_in *)ia->ifa_addr;
					if (memcmp(&sa->sin_addr, &fc.sa_dst.sin_addr, 4) == 0) {
						strncpy(dev_name, ia->ifa_name, sizeof (dev_name) - 1);
						break;
					}
				}
			}
			freeifaddrs(ifas);
		} else {
			fprintf(stderr, "** getifaddrs() failed (%s)\n", strerror(errno));
			return (EHOSTUNREACH);
		}
	} else if (get_route_to_host(&fc.sa_dst.sin_addr, &next_hop, &sa_src.sin_addr, dev_name)) {
		fprintf(stderr, "** couldn't route to host\n");
		return (EHOSTUNREACH);
	}

	memset(&sa_start, 0, sizeof (sa_start));
	sa_start.sin_family = AF_INET;

	memset(&sa_limit, 0, sizeof (sa_limit));
	sa_limit.sin_family = AF_INET;

	if (sstart_str) {
		pc = sstart_str;

		for (pn = pc; *pn && *pn != ':'; pn++);

		len = MIN((pn - pc), (sizeof (tmpbuf) - 2));
		strncpy(tmpbuf, pc, len);
		tmpbuf[len] = 0;

		if (!inet_aton(tmpbuf, &sa_start.sin_addr) ||
			((sa_start.sin_addr.s_addr >> 24) & 255) == 255 ||
			((sa_start.sin_addr.s_addr >> 24) & 255) == 0 ||
			((sa_start.sin_addr.s_addr >> 0) & 255) == 255) {
			fprintf(stderr, "** bad input for source\n");
			return (EINVAL);
		}
		
		sa_start.sin_port = htons((*pn++ && *pn) ? atoi(pn) : 0);

		src_ival = 1;
	}

	if (slim_str) {
		if (!sstart_str) {
			fprintf(stderr, "** bad input for source\n");
			return (EINVAL);
		}

		pc = slim_str;

		for (pn = pc; *pn && *pn != ':'; pn++);

		len = MIN((pn - pc), (sizeof (tmpbuf) - 2));
		strncpy(tmpbuf, pc, len);
		tmpbuf[len] = 0;

		sa_limit.sin_port = htons((*pn++ && *pn) ? atoi(pn) : 0);

		if (!inet_aton(tmpbuf, &sa_limit.sin_addr) ||
			ntohl(sa_limit.sin_addr.s_addr) <= ntohl(sa_start.sin_addr.s_addr) ||
			(sa_limit.sin_port && (!sa_start.sin_port ||
			ntohs(sa_limit.sin_port) <= ntohs(sa_start.sin_port)))) {
			fprintf(stderr, "** bad input for source limit\n");
			return (EINVAL);
		}
		
		src_ival = ntohl(sa_limit.sin_addr.s_addr) - ntohl(sa_start.sin_addr.s_addr);
	}

	ip_start = ntohl(sa_start.sin_addr.s_addr);

	if (sa_start.sin_port) {
		port_start = ntohs(sa_start.sin_port);
		if (sa_limit.sin_port)
			port_ival = ntohs(sa_limit.sin_port) - port_start;
	} else {
		port_start = UDP_EPHEMP_MIN;
		port_ival = UDP_EPHEMP_MAX - UDP_EPHEMP_MIN;
	}

	if (packet_rate <= 0)
		packet_rate = 1;

	if (run_time <= 0)
		run_time = 999999999; /* 30+ years */

	if (fc.app_mode == APP_MODE_ECHO) {
		fprintf(stdout, "running %s echo service on %s:%d\n",
			(fc.proto == IPPROTO_UDP ? "udp" : "tcp"),
			inet_ntoa(fc.sa_dst.sin_addr), ntohs(fc.sa_dst.sin_port));
	} else {
		fprintf(stdout, "running with %d rps for %s:%d\n",
			packet_rate,
			inet_ntoa(fc.sa_dst.sin_addr), ntohs(fc.sa_dst.sin_port));
	}

	fprintf(stdout, "press Esc to cancel..\n");

#ifdef _WIN32
#error UNDONE
#else /* _WIN32 */
	memset(&sa_dev, 0, sizeof (sa_dev));
	if ((sa_dev.sll_ifindex = if_nametoindex(dev_name)) == 0) {
		fprintf(stderr, "** if_nametoindex() failed\n");
		return (EINVAL);
	}

	raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
	if (raw_fd < 0) {
		fprintf(stderr, "** socket() failed with %d\n", errno);
		return (errno);
	}

	memset(&reqbuf, 0, sizeof (reqbuf));
	strcpy(reqbuf.ifr_name, dev_name);

	if (ioctl(raw_fd, SIOCGIFHWADDR, &reqbuf) != -1) {
		fc.src_mac[0] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[0];
		fc.src_mac[1] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[1];
		fc.src_mac[2] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[2];
		fc.src_mac[3] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[3];
		fc.src_mac[4] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[4];
		fc.src_mac[5] = (unsigned char) reqbuf.ifr_hwaddr.sa_data[5];
	} else {
		fprintf(stderr, "** ioctl() failed with %d\n", errno);
		close(raw_fd);
		return (errno);
	}

	sa_dev.sll_family = AF_PACKET;
	memcpy(sa_dev.sll_addr, fc.src_mac, 6);
	sa_dev.sll_halen = htons(6);

	/* set terminal attributes */
	if (tcgetattr(fileno(stdin), &kbd_mode_orig) != 0 ||
		tcgetattr(fileno(stdin), &kbd_mode) != 0) {
		fprintf(stderr, "** tcgetattr() failed with %d\n", errno);
		close(raw_fd);
		return (errno);
	}

	kbd_mode.c_lflag &= ~(ICANON | ECHO);
	kbd_mode.c_cc[VTIME] = 0;
	kbd_mode.c_cc[VMIN] = 0;

	if (tcsetattr(fileno(stdin), TCSANOW, &kbd_mode) != 0) {
		fprintf(stderr, "** tcsetattr() failed with %d\n", errno);
		err = errno;
		close(raw_fd);
		goto loc_exitterm;
	}

	if (fc.app_mode != APP_MODE_ECHO) {
		/* resolve target MAC */

		/* ARP-request */
		pb = arp_req;
		memset(pb, 0xff, 6); pb += 6;
		memcpy(pb, fc.src_mac, 6); pb += 6;
		*(unsigned short *)pb = htons(ETH_P_ARP); pb += 2;
		*(unsigned short *)pb = htons(1); pb += 2; /* hw type */
		*(unsigned short *)pb = htons(ETH_P_IP); pb += 2;
		*pb = 6; pb++; /* hw address length */
		*pb = 4; pb++; /* protocol address length */
		*(unsigned short *)pb = htons(ARPOP_REQUEST); pb += 2;
		memcpy(pb, fc.src_mac, 6); pb += 6; /* sender MAC address */
		memcpy(pb, &sa_src.sin_addr.s_addr, 4); pb += 4;
		memset(pb, 0, 6); pb += 6; /* target MAC address */
		memcpy(pb, &next_hop, 4); pb += 4;

		len = pb - arp_req;
		found_f = 0;
		tmark = 0;

		do {
			/* HA! Y2038 PROBLEM!!1 */
			if (time(NULL) - tmark > ARP_TIMEOUT) {
				if (sendto(raw_fd, arp_req, len, 0, (struct sockaddr *) &sa_dev,
					sizeof (sa_dev)) <= 0) {
					fprintf(stderr, "** sendto() failed with %d\n", errno);
					break;
				}
				tmark = time(NULL);
			}

			arp_to.tv_sec = ARP_TIMEOUT;
			arp_to.tv_usec = 0;

			FD_ZERO(&ralert);
			FD_SET(raw_fd, &ralert);

			err = select(raw_fd + 1, &ralert, NULL, NULL, &arp_to);

			if (err < 0) {
				fprintf(stderr, "** select() failed with %d\n", errno);
			} else if (err) {
				err = recv(raw_fd, packet_data, sizeof (packet_data), 0);
				if (err < 0) {
					fprintf(stderr, "** recv() failed with %d\n", errno);
				} else if (*(unsigned short *)(packet_data + 12) == htons(ETH_P_ARP) &&
					*(unsigned short *)(packet_data + 20) == htons(ARPOP_REPLY) && 
					memcmp(packet_data + 28, &next_hop, 4) == 0 &&
					memcmp(packet_data + 38, &sa_src.sin_addr.s_addr, 4) == 0) {
					memcpy(fc.dst_mac, packet_data + 22, 6);
					found_f = 1;
					break;
				}
			}
		} while (read(fileno(stdin), &ch, 1) <= 0 || ch != 0x1b);

		if (!found_f) {
			fprintf(stderr, "** couldn't resolve %s\n", inet_ntoa(next_hop));
			err = (errno ? errno : ETIMEDOUT);
			close(raw_fd);
			goto loc_exitterm;
		}
	}

	close(raw_fd);
#endif /* _WIN32 */
	/* open workload interface */
	if (pcap_findalldevs(&alldevs, errbuf) == -1) {
		fprintf(stderr, "** pcap_findalldevs() failed with: %s\n", errbuf);
		err = errno;
		goto loc_exitterm;
	} else {
		for (found_f = 0, d = alldevs; d && !found_f; d = d->next) {
			if (strcasecmp(d->name, dev_name) == 0) {
#ifdef _WIN32
				adapter = PacketOpenAdapter(d->name);
				if (adapter) {
					IoCtlBuffLen = (sizeof (PACKET_OID_DATA) + WPCAP_IOCTL_BUFFSIZE);
					OidData =(PPACKET_OID_DATA) GlobalAllocPtr(GMEM_MOVEABLE | GMEM_ZEROINIT, IoCtlBuffLen);
					if (OidData) {
						OidData->Oid = OID_802_3_CURRENT_ADDRESS;
						OidData->Length = WPCAP_IOCTL_BUFFSIZE;
						if (PacketRequest(adapter, FALSE, OidData)) {
							/* take the first */
							/* FIXME: select dynamically depending on target through IP Helper'
							 * GetBestInterface() */
							fc.src_mac[0] = OidData->Data[0]; fc.src_mac[1] = OidData->Data[1];
							fc.src_mac[2] = OidData->Data[2]; fc.src_mac[3] = OidData->Data[3];
							fc.src_mac[4] = OidData->Data[4]; fc.src_mac[5] = OidData->Data[5];
							found_f = 1;
						}
						GlobalFreePtr(OidData);
					}
					PacketCloseAdapter(adapter);
				}
#else /* _WIN32 */
				found_f = 1;
#endif /* _WIN32 */
				break;
			}
		}

		if (d && found_f) {
			fc.pcap_h = pcap_open_live(d->name,
			(snaplen > 0 ? snaplen : get_app_snaplen(fc.app_mode)),
#ifdef _WIN32
					PCAP_OPENFLAG_PROMISCUOUS | PCAP_OPENFLAG_NOCAPTURE_LOCAL,
#else /* _WIN32 */
					/* promiscuous */1,
#endif /* _WIN32 */
					/* UNDONE: rework through select() with pcap fd */
					/* read timeout */0, /* as ms */
					/* FIXME: rework, alien stack will be used */errbuf);
		} else {
			fprintf(stderr, "** couldn't find output device\n");
		}
		pcap_freealldevs(alldevs);
	}

	if (!fc.pcap_h) {
		fprintf(stderr, "** device not ready\n");
		err = errno;
		goto loc_exitterm;
	}

	if (fc.app_mode == APP_MODE_ECHO || fc.proto == IPPROTO_TCP) {
		/* better to use device->addresses->netmask? */
		if (pcap_lookupnet(dev_name, &net_addr, &net_mask, errbuf)) {
			fprintf(stderr, "** pcap_lookupnet() failed: %s\n", errbuf);
			err = errno;
			goto loc_exitdevice;
		}

		if (fc.app_mode == APP_MODE_ECHO) {
			snprintf(tmpbuf, sizeof (tmpbuf) - 1,
				"not ether src %.2x:%.2x:%.2x:%.2x:%.2x:%.2x and ip and %s dst port %d",
				fc.src_mac[0],fc.src_mac[1],fc.src_mac[2],
				fc.src_mac[3],fc.src_mac[4],fc.src_mac[5],
				(fc.proto == IPPROTO_UDP ? "udp" : "tcp"),
				ntohs(fc.sa_dst.sin_port));
		} else {
			snprintf(tmpbuf, sizeof (tmpbuf) - 1, "ip src host %s and tcp%csrc port %d",
				inet_ntoa(fc.sa_dst.sin_addr), (fc.app_mode == APP_MODE_FTP ? 0 : 32),
				ntohs(fc.sa_dst.sin_port));
		}

		if (pcap_compile(fc.pcap_h, &fcode, tmpbuf, /* optimize */1, net_mask)) {
			pcap_perror(fc.pcap_h, "** pcap_compile() failed: ");
			err = errno;
			goto loc_exitdevice;
		}

		if (pcap_setfilter(fc.pcap_h, &fcode)) {
			pcap_perror(fc.pcap_h, "** pcap_setfilter() failed: ");
			err = errno;
			goto loc_exitdevice;
		}

		if (pcap_setnonblock(fc.pcap_h, /* non-blocking */1, errbuf) == -1) {
			fprintf(stderr, "** pcap_setnonblock() failed: %s\n", errbuf);
			err = errno;
			goto loc_exitdevice;
		}
	}

	srand((unsigned) time(NULL));

	if (fc.app_mode == APP_MODE_ECHO) {
		/* UNDONE: */
	} else if (fc.app_mode == APP_MODE_RAW) {
		for (i = 0, pc = query_str; pc && *pc && i < sizeof (arp_req); pc++) {
			if (isxdigit(*pc)) {
				arp_req[i] = (*pc > 57 ? (*pc - 0x57) : (*pc - 48));
				if (isxdigit(*++pc))
					arp_req[i] = (arp_req[i] << 4) | (*pc > 57 ? (*pc - 0x57) : (*pc - 48));
				i++;
			}
		}
		apr.len = i;
		apr.ptr = arp_req;
	} else if (fc.app_mode == APP_MODE_DNS) {
		if (!query_str) {
			/* generate name */
			for (j = k = 0; k < sizeof (dns_parts) / sizeof (dns_parts[0]) &&
				j < sizeof (tmpbuf) - 1; j += i, k++) {
				tmpbuf[j++] = '.';
				for (i = 0; i < dns_parts[k] && j + i < sizeof (tmpbuf) - 1; ) {
					tmpbuf[j + i] = dns_chars[rand() %
						(sizeof (dns_chars)/* w/o trailing zero */- 1)];
					if (!i && tmpbuf[j + i] < 65)
						continue;
					i++;
				}
			}
			tmpbuf[j] = 0;
		} else {
			if (*query_str == '.')
				query_str++;

			tmpbuf[0] = '.';
			strncpy(tmpbuf + 1, query_str, sizeof (tmpbuf) - 2);
		}
		/* have it in NS form */
		for (parg = tmpbuf; parg && *parg; parg = pend) {
			for (pend = parg; pend && *pend && (pend == parg || *pend != '.'); pend++);

			*parg = (unsigned char) (pend > parg ? (pend - parg - 1) : 0);
		}

		/* DNS request */
		/* UNDONE: rework with name list from file */
		apr.ptr = arp_req; pb =(unsigned char *) apr.ptr; /* FIXME: silly, reused to skip on malloc */
		if (fc.proto == IPPROTO_TCP) {
			dns_len =(unsigned short *) pb; pb += 2; *dns_len = 0; 
		}
		*(unsigned short *) pb = /* placeholder */0; pb += 2; /* transaction id */
		*(unsigned short *) pb = htons(0x100); pb += 2; /* standard query,  */
		*(unsigned short *) pb = htons(1); pb += 2; /* questions */
		*(unsigned short *) pb = 0; pb += 2; /* answers */
		*(unsigned short *) pb = 0; pb += 2; /* authorities */
		*(unsigned short *) pb = 0; pb += 2; /* additionals */
		for (pc = tmpbuf; *pc; *pb++ = *pc++); *pb++ = 0; /* requested name */
		*(unsigned short *) pb = 0x100; pb += 2; /* type A */
		*(unsigned short *) pb = 0x100; pb += 2; /* class IN */
		apr.len = pb - apr.ptr;
		if (fc.proto == IPPROTO_TCP) {
			*dns_len = htons((unsigned short)(apr.len -/* RFC1035 4.2.2 */2));
		}
	} else if (fc.app_mode == APP_MODE_FTP) {
		if (!fc.user_name[0] || !fc.passwd[0]) {
				if (fc.user_name[0] || fc.passwd[0]) {
				fprintf(stderr, "** bogus ftp credentials\n");
				err = EINVAL;
				goto loc_exitdevice;
			}
			/* anonymous access */
			strcpy(fc.user_name, "ftp");
			strcpy(fc.passwd, "ftp");
		}
		apr.len = snprintf((char *) arp_req, sizeof (arp_req) - 1, "%s\r\n",
			(query_str ? query_str : "LIST"));
		apr.ptr = arp_req; 
	} else if (fc.app_mode == APP_MODE_HTTP) {
		apr.len = snprintf((char *) arp_req, sizeof (arp_req) - 1,
			"GET /%s HTTP/1.1\r\n"
			"Accept: */*\r\n"
			"Accept-Language: en-us\r\n"
			"Accept-Encoding: gzip, deflate\r\n"
			"User-Agent: %s\r\n"
			"Host: %s\r\n"
			"Connection: %s\r\n"
			"\r\n",
			(query_str ? query_str : ""),
			(ua_str ? ua_str : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"),
			inet_ntoa(fc.sa_dst.sin_addr),
			(fc.linger_time ? "Keep-Alive" : "close"));
		apr.ptr = arp_req; 
	} else if (fc.app_mode == APP_MODE_TELNET) {
		/* UNDONE: */
		assert(0);
	}

	/* setup frame context */
	err = 0, fc.rcount = 0, fc.num_spawn = 0;
	fc.id = (unsigned short) rand();
	fc.raw_data = packet_data;
	fc.ether_hdr = fc.raw_data;
	pb = fc.ether_hdr;
	memcpy(pb, fc.dst_mac, 6); pb += 6;
	memcpy(pb, fc.src_mac, 6); pb += 6;
	*(unsigned short *) pb = htons(ETH_P_IP); pb += 2;
	fc.ether_hdr_len = /* eth hdr len */14;
	fc.ip_hdr = pb;
	fc.areqs = &apr;
	fc.err_notify = 0;

#ifdef _WIN32
#error UNDONE
#else /* _WIN32 */
	clock_gettime(CLOCK_REALTIME, &ts);
	stop_ts = ts;
	stop_ts.tv_sec += run_time;
	inter_ts = ts;
#endif /* _WIN32 */

	/* main cycle */
	if (fc.app_mode == APP_MODE_ECHO) {
		fkstream_t disp;
		do {
			tofetch = PKT_TO_FETCH;
			while (--tofetch && pcap_next_ex(fc.pcap_h, &pkt_hdr, &pkt_data) > 0) {
				unsigned short total_len = ntohs(*(unsigned short *) (pkt_data + 0x10)), /* ip total len */
					ip_len = ((*(pkt_data + fc.ether_hdr_len) & 0xf) << 2), /* ip hdr len */
					user_len = total_len - ip_len;

				const unsigned char *trans_hdr = pkt_data + fc.ether_hdr_len + ip_len;

				memcpy(/*fc.dst_mac*/fc.ether_hdr, pkt_data + 6, sizeof (fc.dst_mac));
				disp.dst_ip.s_addr = *(unsigned int *) (pkt_data + 0x1a); /* incoming source */
				disp.ip.s_addr = *(unsigned int *) (pkt_data + 0x1e); /* incoming dest */
				disp.dst_port = *(unsigned short *) (trans_hdr);
				disp.src_port = *(unsigned short *) (trans_hdr + 2);

				if (fc.proto == IPPROTO_TCP) {
					unsigned char outf, flags = *(trans_hdr + 0x0d); /* LSB portion */

					if ((flags & (/* PSH | SYN | FIN */0xb)) == 0)
						continue;

					user_len -= ((*(trans_hdr + 0x0c) & 0xf0) >> 2); /* tcp hdr len */

					disp.ack = ntohl(*(unsigned int *) (trans_hdr + 4)) + user_len; /* incoming seq */
					disp.seq = ntohl(*(unsigned int *) (trans_hdr + 8)); /* incoming ack */

					ip_frame(&fc, &disp.ip, &disp.dst_ip);

					if (flags & /* SYN */2) {
						outf = /* SYN | ACK */0x12;
						disp.seq =(unsigned int) rand();
						disp.ack++;
					} else if (flags & /* PSH */8) {
						outf = /* ACK */0x10;
					} else if (flags * /* FIN */1) {
						disp.ack++;
						outf = /* FIN | ACK */0x11;
					} else {
						assert(0);
					}

					tcp_frame(&fc, &disp, outf);

				} else {
					user_len = ntohs(*(unsigned short *) (trans_hdr + 4)); /* udp len */

					ip_frame(&fc, &disp.ip, &disp.dst_ip);

					fc.trans_len = MIN(user_len, pkt_hdr->caplen - fc.ether_hdr_len - ip_len);

					memcpy(fc.trans_hdr + 8, trans_hdr + 8, fc.trans_len - 8); /* udp hdr len */

					udp_frame(&fc, &disp, fc.trans_len);
				}

				fc.length = fc.ether_hdr_len + fc.ip_hdr_len + fc.trans_len; 
				*fc.ip_len = htons(fc.ip_hdr_len + fc.trans_len);

				cksum_frame(&fc);

				if (pcap_sendpacket(fc.pcap_h, fc.raw_data, fc.length)) {
					fprintf(stderr, "** pcap_sendpacket() failed (%s)\n", pcap_geterr(fc.pcap_h));
					break;
				}
			}
		} while (!(read(fileno(stdin), &ch, 1) > 0 && ch == 0x1b));

		goto loc_exitdevice;

	} else do {
		/* src ip */
		if (!src_ival) {
			ip_val =(unsigned int) sa_src.sin_addr.s_addr;
		} else if (src_ival > 1) {
			ip_val = ip_start + (unsigned int) rand() % src_ival;
			if ((ip_val & 255) == 255)
				ip_val += 2;
			else if ((ip_val & 255) == 0)
				ip_val++;
			if ((ip_val >> 24) == 255)
				ip_val = ip_start;
			ip_val = htonl(ip_val);
		} else {
			ip_val =(unsigned int) sa_start.sin_addr.s_addr;
		}
		/* src port */
		if (port_ival) {
			p_val = htons((unsigned short) (port_start + (rand() % port_ival)));
		} else {
			p_val = htons(port_start);
		}

		if (fc.proto == IPPROTO_TCP) {
			if (!num_requests ||
				(fks_nodes->size + fc.rcount + fc.num_spawn) < num_requests) {
				/* add new conn */
				fkstream_t *sp;
				uint64_t key = hap_key(ip_val, p_val, fc.sa_dst.sin_port, fc.app_mode);
				khiter_t it = kh_get(fksname, fks_nodes, key);
				if (it == kh_end(fks_nodes)) {
					endp_tries = 0;
					it = kh_put(fksname, fks_nodes, key, &res);
					if (res) {
						sp = &kh_value(fks_nodes, it);
						sp->ip.s_addr = ip_val;
						sp->src_port = p_val;
						sp->dst_port = fc.sa_dst.sin_port;
						sp->stage = FKS_SPAWN;
						sp->ts_sec = ts.tv_sec;
						sp->ts_spawn = ts.tv_sec;
						sp->lp_key = 0;
						sp->iname = 0; /* UNDONE: choose random if name list */
					} else {
						/* FIXME: notify user, retry? */
						kh_del(fksname, fks_nodes, it);
					}
				} else if (endp_tries++ < ENDP_NUM_TRIES) {
					continue;
				} else {
					/* UNDONE: notify user on endpoints exhaustion */
					/*sp = &kh_value(fks_nodes, it);*/
					endp_tries = 0;
				}
			}
		} else { /* IPPROTO_UDP */
			/* at least one packet will be sent */
			ip_frame(&fc, (struct in_addr *) &ip_val, &fc.sa_dst.sin_addr);
			pb = fc.trans_hdr;

			/* UDP header */
			*(unsigned short *) pb = p_val; pb += 2; /* src port */
			*(unsigned short *) pb = fc.sa_dst.sin_port; pb += 2; /* dst port */
			udp_len =(unsigned short *) pb; pb += 2; /* length */
			fc.trans_cksum =(unsigned short *) pb; pb += 2; /* checksum */

			memcpy(pb, apr.ptr, apr.len);
			if (fc.app_mode == APP_MODE_DNS)
				*(unsigned short *) pb =(unsigned short) rand(); /* transaction id */
			pb += apr.len;

			/* lengths + chksums */
			fc.length = pb - fc.raw_data;
			*fc.ip_len = htons(fc.length - fc.ether_hdr_len);
			*udp_len = htons(fc.length - fc.ether_hdr_len - fc.ip_hdr_len);

			cksum_frame(&fc);

			if (pcap_sendpacket(fc.pcap_h, fc.raw_data, fc.length)) {
				fprintf(stderr, "** pcap_sendpacket() failed (%s)\n", pcap_geterr(fc.pcap_h));
				err = errno;
				/* UNDONE: consider not fatal?? */
				break;
			} else {
				fc.rcount++;
			}
		}
#ifdef _WIN32
#error UNDONE
#else /* _WIN32 */
		timespec_add_ns32(&ts, 1000000000l / packet_rate); /* pps */

		do {
			if (fc.proto == IPPROTO_TCP) {
				/* UNDONE: use adaptive thing based on intervals */
				tofetch = PKT_TO_FETCH;
				while (--tofetch && pcap_next_ex(fc.pcap_h, &pkt_hdr, &pkt_data) > 0)
					parse_foreign_frame(pkt_hdr, pkt_data, fks_nodes, &inter_ts, fc.app_mode);

				/* update streams */
				fks_tick(fks_nodes, &fc, &inter_ts);
			}

			clock_gettime(CLOCK_REALTIME, &inter_ts);

		} while ((unsigned long) inter_ts.tv_sec < (unsigned long) ts.tv_sec ||
			((unsigned long) inter_ts.tv_sec == (unsigned long) ts.tv_sec &&
			(unsigned long) inter_ts.tv_nsec < (unsigned long) ts.tv_nsec));
#endif /* _WIN32 */
	} while (
#ifdef _WIN32
#error UNDONE
#else /* _WIN32 */
		!(num_requests && fc.rcount >= num_requests) &&
		!((unsigned long) ts.tv_sec > (unsigned long) stop_ts.tv_sec ||
		((unsigned long) ts.tv_sec == (unsigned long) stop_ts.tv_sec &&
		(unsigned long) ts.tv_nsec >= (unsigned long) stop_ts.tv_nsec) ||
		(read(fileno(stdin), &ch, 1) > 0 && ch == 0x1b))
#endif /* _WIN32 */
	);

	/* kill stuck connections */
	if (fc.proto == IPPROTO_TCP) {
		/* force timeout */
		ts.tv_sec += 2 * fc.app_timo;
		/* send resets */
		fks_tick(fks_nodes, &fc, &ts);
		/* clean streams */
		fks_tick(fks_nodes, &fc, &ts);
	}

	fprintf(stdout, "put %llu request(s)\n", fc.rcount);

loc_exitdevice:
	pcap_close(fc.pcap_h);

#ifdef _WIN32
#error UNDONE
#else /* _WIN32 */
loc_exitterm:
	if (tcsetattr(fileno(stdin), TCSANOW, &kbd_mode_orig) != 0)
		fprintf(stderr, "** tcsetattr() failed with %d\n", errno);
#endif /* _WIN32 */

    kh_destroy(fksname, fks_nodes);

	return (err);
}

/* end */
